#ifndef XG_MYSQLCONNECT_CPP
#define XG_MYSQLCONNECT_CPP
//////////////////////////////////////////////////////////////
#include "../MySQLConnect.h"

#ifdef _MSC_VER
#pragma comment(lib, "libmysql.lib")
#endif

MySQLRowData::MySQLRowData(MYSQL_ROW row, MYSQL_RES* res)
{
	m_bNull = true;
	m_pSQLRow = row;
	m_pSQLRes = res;
}
bool MySQLRowData::isNull()
{
	return m_bNull;
}
int MySQLRowData::getData(int index, char* data, int len)
{
	int sz = getDataLength(index);

	if (sz <= 0) return sz;
	if (len > sz) len = sz;
	
	memcpy(data, m_pSQLRow[index], len);
	
	return len;
}
int MySQLRowData::getDataLength(int index)
{
	m_bNull = m_pSQLRow[index] == NULL;

	return m_bNull ? 0 : mysql_fetch_lengths(m_pSQLRes)[index];
}
string MySQLRowData::getString(int index)
{
	m_bNull = m_pSQLRow[index] == NULL;
	
	return m_bNull ? stdx::EmptyString() : m_pSQLRow[index];
}

MySQLQueryResult::MySQLQueryResult(MYSQL_RES* res, DBConnect* conn)
{
	m_pSQLRes = res;
	m_pConn = conn;
}
MySQLQueryResult::~MySQLQueryResult()
{
	close();
}
void MySQLQueryResult::close()
{
	if (m_pSQLRes)
	{
		mysql_free_result(m_pSQLRes);
		m_pSQLRes = NULL;
	}
}
int MySQLQueryResult::rows()
{
	return mysql_num_rows(m_pSQLRes);
}
int MySQLQueryResult::cols()
{
	return mysql_num_fields(m_pSQLRes);
}
string MySQLQueryResult::getColumnName(int index)
{
	MYSQL_FIELD* field = mysql_fetch_field_direct(m_pSQLRes, index);
	
	if (field == NULL) return stdx::EmptyString();

	return field->name;
}
sp<RowData> MySQLQueryResult::next()
{
	MYSQL_ROW row = mysql_fetch_row(m_pSQLRes);

	if (row == NULL) return NULL;

	return newsp<MySQLRowData>(row, m_pSQLRes);
}
bool MySQLQueryResult::seek(int ofs)
{
	mysql_data_seek(m_pSQLRes, ofs);

	return mysql_errno(m_pSQLRes->handle) == 0;
}
bool MySQLQueryResult::getColumnData(ColumnData& data, int index)
{
	MYSQL_FIELD* field = mysql_fetch_field_direct(m_pSQLRes, index);

	CHECK_FALSE_RETURN(field);

	data.type = DBData_String;
	data.size = field->length;
	data.scale = field->decimals;

	strncpy(data.name, field->name, sizeof(data.name));
	data.name[sizeof(data.name) - 1] = 0;

	if (IS_NUM(field->type))
	{
		if (data.scale == 0)
		{
			data.type = DBData_Integer;
		}
		else
		{
			data.type = DBData_Float;
		}
	}
	else if (field->type == FIELD_TYPE_DATETIME || field->type == FIELD_TYPE_TIMESTAMP)
	{
		data.type = DBData_DateTime;
	}
	else if (field->type == FIELD_TYPE_BLOB)
	{
		data.type = DBData_Blob;
	}

	if (field->flags & NOT_NULL_FLAG)
	{
		data.nullable = false;
	}
	else
	{
		data.nullable = true;
	}

	return true;
}
int MySQLQueryResult::getErrorCode()
{
	return m_pConn ? m_pConn->getErrorCode() : XG_ERROR;
}
string MySQLQueryResult::getErrorString()
{
	return m_pConn->getErrorString();
}
	

MySQLConnect::MySQLConnect()
{
	m_pConn = NULL;
}
MySQLConnect::~MySQLConnect()
{
	close();
}
bool MySQLConnect::commit()
{
	return mysql_commit(m_pConn) == 0;
}
bool MySQLConnect::rollback()
{
	return mysql_rollback(m_pConn) == 0;
}
bool MySQLConnect::begin(bool commited)
{
	if (commited) CHECK_FALSE_RETURN(commit());

	return mysql_autocommit(m_pConn, 0) == 0 && mysql_query(m_pConn, "BEGIN") == 0;
}
const char* MySQLConnect::getSystemName()
{
	return "MySQL";
}
bool MySQLConnect::setCharset(const string& charset)
{
	mysql_options(m_pConn, MYSQL_SET_CHARSET_NAME, charset.c_str());

	return getErrorCode() == 0;
}
bool MySQLConnect::connect(const string& host, int port, const string& name, const string& usr, const string& passwd)
{
	close();

	mysql_thread_init();
	
	CHECK_FALSE_RETURN(m_pConn = mysql_init(NULL));

	setCharset(stdx::replace(GetLocaleCharset(), "-", ""));

	return mysql_real_connect(m_pConn, host.c_str(), usr.c_str(), passwd.c_str(), name.c_str(), port, NULL, 0) ? true : false;
}
void MySQLConnect::close()
{
	if (m_pConn)
	{
		mysql_close(m_pConn);
		mysql_thread_end();
		m_pConn = NULL;
	}
}
sp<QueryResult> MySQLConnect::query(const string& sqlcmd)
{
	if (sqlcmd.empty()) return NULL;

	if (mysql_query(m_pConn, sqlcmd.c_str()))
	{
		updateStatus(SQLRtn_Error, sqlcmd);

		return NULL;
	}

	MYSQL_RES* res = mysql_store_result(m_pConn);
	
	if (res == NULL)
	{
		updateStatus(SQLRtn_Error, sqlcmd);

		return NULL;
	}

	sp<QueryResult> rs = newsp<MySQLQueryResult>(res, this);
	
	updateStatus(rs->rows(), sqlcmd);

	return rs;
}
sp<QueryResult> MySQLConnect::quickQuery(const string& sqlcmd)
{
	if (sqlcmd.empty()) return NULL;

	if (mysql_query(m_pConn, sqlcmd.c_str()))
	{
		updateStatus(SQLRtn_Error, sqlcmd);

		return NULL;
	}

	MYSQL_RES* res = mysql_use_result(m_pConn);

	if (res == NULL)
	{
		updateStatus(SQLRtn_Error, sqlcmd);

		return NULL;
	}

	sp<QueryResult> rs = newsp<MySQLQueryResult>(res, this);
	
	updateStatus(rs->rows(), sqlcmd);

	return rs;
}
int MySQLConnect::execute(const string& sqlcmd)
{
	if (sqlcmd.empty()) return XG_PARAMERR;

	if (mysql_query(m_pConn, sqlcmd.c_str()))
	{
		int err = getErrorCode();

		updateStatus(SQLRtn_Error, sqlcmd);

		if (err == ER_DUP_KEY || err == ER_DUP_ENTRY) return SQLRtn_Duplicate;

		if (err == ER_KEY_NOT_FOUND) return SQLRtn_NotFound;

		return SQLRtn_Error;
	}

	return updateStatus(mysql_affected_rows(m_pConn), sqlcmd);
}
int MySQLConnect::getErrorCode()
{
	return mysql_errno(m_pConn);
}
string MySQLConnect::getErrorString()
{
	return m_pConn ? string(mysql_error(m_pConn)) : stdx::EmptyString();
}
int MySQLConnect::getPrimaryKeys(vector<string>& vec, const string& tabname)
{
	string sql = "SELECT * FROM " + tabname + " WHERE 1=0";

	if (mysql_query(m_pConn, sql.c_str())) return -1;

	MYSQL_RES* res = mysql_store_result(m_pConn);
	
	if (res == NULL) return -1;

	int cnt = mysql_num_fields(res);

	if (cnt <= 0)
	{
		mysql_free_result(res);
		
		return -1;
	}
	
	for (int i = 0; i < cnt; i++)
	{
		MYSQL_FIELD* field = mysql_fetch_field_direct(res, i);
		
		if (field == NULL)
		{
			mysql_free_result(res);
			
			return -1;
		}
		
		if (field->flags & PRI_KEY_FLAG)
		{
			vec.push_back(field->name);
		}
	}
	
	if (vec.empty())
	{
		for (int i = 0; i < cnt; i++)
		{
			MYSQL_FIELD* field = mysql_fetch_field_direct(res, i);
			
			if (field == NULL)
			{
				mysql_free_result(res);
				
				return -1;
			}
			
			vec.push_back(field->name);
		}
	}
	
	mysql_free_result(res);

	return vec.size();
}
int MySQLConnect::getTables(vector<string>& vec)
{
	const char* sql = "SELECT table_name FROM information_schema.tables WHERE table_type='base table' AND table_schema IN (SELECT DATABASE())";
	
	sp<QueryResult> rs = query(sql);

	vec.clear();
	
	if (rs)
	{
		sp<RowData> row;

		while (row = rs->next()) vec.push_back(row->getString(0));
	}

	return vec.size();
}
string MySQLConnect::translate(const string& sqlcmd, const vector<DBData*>& vec)
{
	int sz;
	string str;
	u_long len = 0;
	size_t idx = 0;
	const char* src;
	SmartBuffer buffer;
	string bindstr = getBindString();
	const char* sql = sqlcmd.c_str();
	const char* name = getSystemName();

	while (*sql)
	{
		if (memcmp(sql, bindstr.c_str(), bindstr.length()))
		{
			str += *sql++;
		}
		else
		{
			DBData* item = vec[idx];
			E_DBDATA_TYPE type = item->type();

			if (idx >= vec.size())
			{
				updateStatus(XG_PARAMERR, sqlcmd, vec);

				return stdx::EmptyString();
			}

			if (item->isNull())
			{
				str += isupper(sqlcmd[0]) ? "NULL" : "null";
			}
			else if (type == DBData_Float || type == DBData_Integer || type == DBData_DateTime)
			{
				str += item->toValueString(name);
			}
			else
			{
				if (type == DBData_Blob)
				{
					DBBlob* data = (DBBlob*)(item);

					src = data->val().str();
					sz = data->size();
				}
				else
				{
					DBString* data = (DBString*)(item);
					
					src = data->val().c_str();
					sz = data->val().length();
				}

				if (buffer.size() < (int)(len = sz * 2 + 4)) buffer.malloc(len);

				len = mysql_real_escape_string(m_pConn, buffer.str() + 1, src, sz); 
				
				if (len < 0)
				{
					updateStatus(XG_SYSERR, sqlcmd, vec);

					return stdx::EmptyString();
				}

				buffer[len + 1] = buffer[0] = '\'';
				buffer[len + 2] = 0;
				str += buffer.str();
			}

			sql += bindstr.length();

			++idx;
		}
	}

	return str;
}
sp<QueryResult> MySQLConnect::query(const string& sqlcmd, const vector<DBData*>& vec)
{
	return query(translate(sqlcmd, vec));
}
int MySQLConnect::execute(const string& sqlcmd, const vector<DBData*>& vec)
{
	return execute(translate(sqlcmd, vec));
}

bool MySQLConnect::Setup()
{
	mysql_library_init(0, NULL, NULL);

	return true;
}
//////////////////////////////////////////////////////////////
#endif
